/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.core.utils
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public class ByteArrayUtils
	{

		private static const CHAR_TABLE:Vector.<uint> = Vector.<uint>([0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
			0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
			0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
			0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,]);

		private static const HEX_TABLE:Vector.<String> = Vector.<String>(["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
			"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
			"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
			"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
			"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
			"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
			"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
			"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
			"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
			"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
			"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
			"b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
			"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
			"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df",
			"e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
			"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",]);

		private static const VALUE_TABLE:Vector.<uint> = Vector.<uint>([0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3e, 0x40, 0x40, 0x40, 0x3f,
			0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x40, 0x40, 0x40, 0xff, 0x40, 0x40,
			0x40, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
			0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
			0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
			0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,]);

		private static var _base64Buffer:ByteArray = new ByteArray();

		public static function decodeBase64(encoded:String):ByteArray
		{
			if (encoded === null)
			{
				return new ByteArray();
			}

			var pos:uint = 0;
			var idx:uint = 0;
			var data:ByteArray = new ByteArray();
			var table:Vector.<uint> = VALUE_TABLE;
			var buf:ByteArray = _base64Buffer;
			var b0:uint, b1:uint, b2:uint, b3:uint;
			var i:uint, j:uint, l0:uint, l1:uint, v:uint;

			for (i = 0, l0 = Math.ceil(encoded.length / 1024); i < l0; i++)
			{
				buf.position = 0;
				buf.writeUTFBytes(encoded.substr(i * 1024, 1024));

				for (j = 0, l1 = buf.position; j < l1; j++)
				{
					v = table[buf[j]];
					if (v === 0x40)
					{
						continue;
					}

					pos === 0 ? (b0 = v) : pos === 1 ? (b1 = v) : pos === 2 ? (b2 = v) : (b3 = v);
					if (++pos !== 4)
					{
						continue;
					}

					data[idx] = (b0 << 2) | (b1 >> 4);
					if (b2 === 0xff)
					{
						break;
					}
					data[idx + 1] = (b1 << 4) | (b2 >> 2);
					if (b3 === 0xff)
					{
						break;
					}
					data[idx + 2] = (b2 << 6) | b3;

					pos = 0;
					idx += 3;
				}
			}

			return data;
		}

		public static function encodeBase64(data:ByteArray, newLine:Boolean = false):String
		{
			if (data === null)
			{
				return "";
			}

			var count:uint = 0;
			var pos:uint = 0;
			var len:uint = int(data.length / 3) * 3;
			var remain:uint = data.length - len;
			var table:Vector.<uint> = CHAR_TABLE;
			var buf:ByteArray = _base64Buffer;
			var encoded:String = "";
			var i:uint, b0:uint, b1:uint, b2:uint;

			for (i = 0; i < len; i += 3)
			{
				if (newLine && ++count === 20)
				{
					buf[pos++] = 0x0a;
					count = 1;
				}

				b0 = data[i];
				b1 = data[i + 1];
				b2 = data[i + 2];
				buf[pos] = table[(b0 & 0xfc) >> 2];
				buf[pos + 1] = table[((b0 & 0x03) << 4) | (b1 >> 4)];
				buf[pos + 2] = table[((b1 & 0x0f) << 2) | (b2 >> 6)];
				buf[pos + 3] = table[b2 & 0x3f];
				pos += 4;

				if ((i & 0x1ff) === 0x1ff)
				{
					buf.position = 0;
					encoded += buf.readUTFBytes(pos);
					pos = 0;
				}
			}

			if (remain > 0)
			{
				if (newLine && ++count === 20)
				{
					buf[pos++] = 0x0a;
				}

				b0 = data[i++];
				buf[pos] = table[(b0 & 0xfc) >> 2];
				buf[pos + 3] = 0x3d;

				if (remain === 1)
				{
					buf[pos + 1] = table[(b0 & 0x03) << 4];
					buf[pos + 2] = 0x3d;
				}
				else
				{
					b1 = data[i++];
					buf[pos + 1] = table[((b0 & 0x03) << 4) | (b1 >> 4)];
					buf[pos + 2] = table[(b1 & 0x0f) << 2];
				}
				pos += 4;
			}

			if (pos > 0)
			{
				buf.position = 0;
				encoded += buf.readUTFBytes(pos);
			}

			return encoded;
		}

		public static function hexDump(bytes:ByteArray):String
		{
			var result:String = "         00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n";
			result += "--------+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--";

			for (var i:int = 0, len:uint = bytes.length; i < len; i++)
			{
				if (i % 16 === 0)
				{
					result += "\n";
					result += ("0000000" + uint(i / 16).toString(16).toLowerCase()).substr(-6);
					result += "0:";
				}

				result += " ";
				result += HEX_TABLE[uint(bytes[i])];
			}

			return result;
		}

		public static function isSameContent(bytes0:ByteArray, bytes1:ByteArray):Boolean
		{
			if (bytes0 === bytes1)
			{
				return true;
			}
			if (bytes0 === null || bytes1 === null)
			{
				return false;
			}
			if (bytes0.length !== bytes1.length)
			{
				return false;
			}

			var pos0:uint = bytes0.position;
			var pos1:uint = bytes1.position;
			var end0:String = bytes0.endian;
			var end1:String = bytes1.endian;
			var result:Boolean = true;
			var len:uint = bytes0.length / 4;
			var remain:uint = bytes0.length - len * 4;
			var i:uint;

			bytes0.position = 0;
			bytes1.position = 0;
			bytes0.endian = Endian.BIG_ENDIAN;
			bytes1.endian = Endian.BIG_ENDIAN;

			for (i = 0; i < len; i++)
			{
				if (bytes0.readInt() !== bytes1.readInt())
				{
					result = false;
					break;
				}
			}

			if (result)
			{
				for (i = len * 4, len = len * 4 + remain; i < len; i++)
				{
					if (bytes0[i] !== bytes1[i])
					{
						result = false;
						break;
					}
				}
			}

			bytes0.position = pos0;
			bytes1.position = pos1;
			bytes0.endian = end0;
			bytes1.endian = end1;

			return result;
		}

		{
			CHAR_TABLE.fixed = true;
			VALUE_TABLE.fixed = true;
			_base64Buffer.length = 4096;
		}
	}
}
